
package com.hlkj.pay.gateway.order.impl;
/*
 * Hlpay-Plus aggregate payment system.
 * Copyright (c) 2024-2025 Hlpay Team Copyright has the right of final interpretation.
 */

import com.hlkj.framework.common.pojo.CommonResult;
import com.hlkj.framework.extension.executor.ExtensionExecutor;
import com.hlkj.framework.extension.scenario.BizScenario;
import com.hlkj.pay.app.common.IUploadService;
import com.hlkj.pay.app.merchant.ext.ChannelMerchantPaymentExtPt;
import com.hlkj.pay.app.merchant.ext.MerchantExtConstant;
import com.hlkj.pay.app.merchant.ext.dto.payment.ChannelPaymentAccountContext;
import com.hlkj.pay.app.merchant.ext.dto.payment.ChannelPaymentCallBackContext;
import com.hlkj.pay.app.merchant.ext.dto.payment.ChannelPaymentCallBackDto;
import com.hlkj.pay.app.merchant.ext.dto.payment.ChannelPaymentContext;
import com.hlkj.pay.dto.merchant.MerchantAcctBalanceDto;
import com.hlkj.pay.dto.payment.MerchantPaymentDto;
import com.hlkj.pay.dto.payment.MerchantPaymentQueryDto;
import com.hlkj.pay.dto.payment.MerchantProofDto;
import com.hlkj.pay.enums.CommonEnum;
import com.hlkj.pay.enums.MerchantEnum;
import com.hlkj.pay.enums.PayEnums;
import com.hlkj.pay.gateway.order.IChannelPaymentGateway;
import com.hlkj.pay.infrastructure.model.merchant.MerchantAppChannelDO;
import com.hlkj.pay.infrastructure.model.pay.PayChannelRequestDO;
import com.hlkj.pay.infrastructure.model.pay.ProviderChannelConfigDO;
import com.hlkj.pay.infrastructure.model.payment.MerchantPaymentDO;
import com.hlkj.pay.infrastructure.model.payment.MerchantPaymentDetailDO;
import com.hlkj.pay.service.merchant.IMerchantAccessService;
import com.hlkj.pay.service.merchant.IMerchantApplicationService;
import com.hlkj.pay.service.pay.IPayChannelService;
import com.hlkj.pay.service.pay.IProviderService;
import com.hlkj.pay.util.FileUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;

/**
 * @author HlpayTeam
 * @date 2024/11/20 16:34
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class ChannelPaymentGatewayImpl implements IChannelPaymentGateway {

    @Value("${hlkj.pay.barrier:false}")
    private boolean barrier;

    @Value("${hlkj.pay.test:false}")
    private boolean test;

    @Value("${hlkj.domain}")
    private String domain;

    private final IMerchantApplicationService merchantApplicationService;

    private final ExtensionExecutor extensionExecutor;

    private final IPayChannelService payChannelService;

    private final IProviderService providerService;

    private final IMerchantAccessService merchantAccessService;

    private final IUploadService uploadService;

    @Override
    public void submitChannelPayment(MerchantPaymentDto merchantPaymentDto) {
        if (barrier) {
            log.info("测试环境提交代付 merchantPaymentDto:{}", merchantPaymentDto.getPaymentId());
            merchantPaymentDto.getMerchantPaymentDetailList().forEach(merchantPaymentDetailDO -> {
                merchantPaymentDetailDO.setStatus(MerchantEnum.PROCESS_STATUS.FAIL.getCode());
            });
            return;
        }
        MerchantAppChannelDO merchantAppChannelDO = merchantApplicationService.queryAppChannelDetail(merchantPaymentDto.getMchChannelCode());
        ChannelPaymentContext channelPaymentContext = new ChannelPaymentContext();
        channelPaymentContext.setMerchantAppChannelDO(merchantAppChannelDO);
        channelPaymentContext.setMerchantPaymentDO(merchantPaymentDto);
        channelPaymentContext.setNotifyUrl(domain + MerchantExtConstant.MERCHANT_PAYMENT_ASYNC_CALLBACK_URL + merchantPaymentDto.getPaymentId());
        channelPaymentContext.setTest(test);
        channelPaymentContext.setProviderChannelConfig(providerService.queryConfig(merchantAppChannelDO.getProviderCode(), merchantAppChannelDO.getChannelCode()));
        for (MerchantPaymentDetailDO merchantPaymentDetailDO : merchantPaymentDto.getMerchantPaymentDetailList()) {
            channelPaymentContext.setMerchantPaymentDetailDO(merchantPaymentDetailDO);
            channelPaymentContext.setProcessStatus(true);
            channelPaymentContext.setRemark(null);
            try {
                extensionExecutor.executeVoid(ChannelMerchantPaymentExtPt.class, BizScenario.valueOf(merchantAppChannelDO.getChannelCode()),
                        ex -> ex.submitChannelSinglePayment(channelPaymentContext));
            }
            catch (Exception e) {
                e.printStackTrace();
                log.error("submitChannelPayment submitChannelSinglePayment error", e);
                channelPaymentContext.setProcessStatus(false);
                channelPaymentContext.setRemark(e.getMessage());
            }
            if (!channelPaymentContext.isProcessStatus()) {
                log.info("代付提交即失败 detailPaymentId:{}", merchantPaymentDetailDO.getDetailPaymentId());
                merchantPaymentDetailDO.setStatus(MerchantEnum.PROCESS_STATUS.FAIL.getCode());
                merchantPaymentDetailDO.setRemark(channelPaymentContext.getRemark());
            }
            else {
                merchantPaymentDetailDO.setStatus(MerchantEnum.PROCESS_STATUS.DOING.getCode());
                merchantPaymentDetailDO.setRemark(channelPaymentContext.getRemark());
            }
            PayChannelRequestDO payChannelRequestDO = channelPaymentContext.getPayChannelRequestDO();
            if (payChannelRequestDO != null) {
                payChannelRequestDO.setSn(merchantPaymentDto.getSn());
                payChannelRequestDO.setProviderCode(merchantPaymentDto.getProviderCode());
                payChannelRequestDO.setChannelCode(merchantPaymentDto.getChannelCode());
                payChannelRequestDO.setMchChannelCode(merchantPaymentDto.getMchChannelCode());
                payChannelRequestDO.setRelationId(merchantPaymentDetailDO.getDetailPaymentId());
                payChannelService.saveRequest(payChannelRequestDO, PayEnums.CHANNEL_REQUEST_TYPE.PAYMENT);
            }
        }
    }

    @Override
    public List<MerchantAcctBalanceDto> queryChannelPaymentAccount(String mchChannelCode, List<Integer> acctTypeList) {
        if (barrier) {
            log.info("测试环境查询代付余额 merchantPaymentDto:{}", mchChannelCode);
            MerchantAcctBalanceDto merchantAcctBalanceDto = new MerchantAcctBalanceDto();
            merchantAcctBalanceDto.setAcctType(1);
            merchantAcctBalanceDto.setBalance(new BigDecimal(Integer.MAX_VALUE));
            return new ArrayList<>() {

                {
                    add(merchantAcctBalanceDto);
                }
            };
        }
        MerchantAppChannelDO merchantAppChannelDO = merchantApplicationService.queryAppChannelDetail(mchChannelCode);
        ChannelPaymentAccountContext channelPaymentAccountContext = new ChannelPaymentAccountContext();
        channelPaymentAccountContext.setMerchantAppChannelDO(merchantAppChannelDO);
        channelPaymentAccountContext.setAcctTypeList(acctTypeList);
        channelPaymentAccountContext.setTest(test);
        channelPaymentAccountContext.setProviderChannelConfig(providerService.queryConfig(merchantAppChannelDO.getProviderCode(), merchantAppChannelDO.getChannelCode()));
        extensionExecutor.executeVoid(ChannelMerchantPaymentExtPt.class, BizScenario.valueOf(merchantAppChannelDO.getChannelCode()),
                ex -> ex.queryChannelAccount(channelPaymentAccountContext));
        return channelPaymentAccountContext.getMerchantAcctBalances();
    }

    @Override
    public MerchantPaymentDetailDO queryChannelPaymentDetail(MerchantPaymentQueryDto merchantPaymentQueryDto) {
        MerchantPaymentDetailDO merchantPaymentDetailQuery = new MerchantPaymentDetailDO();

        MerchantPaymentDetailDO merchantPaymentDetailDO = merchantPaymentQueryDto.getMerchantPaymentDetail();
        if (barrier) {
            log.info("测试环境查询代付状态 detailPaymentId:{}", merchantPaymentDetailDO.getDetailPaymentId());
            merchantPaymentDetailQuery.setStatus(MerchantEnum.PROCESS_STATUS.SUCCESS.getCode());
            return merchantPaymentDetailQuery;
        }
        MerchantAppChannelDO merchantAppChannelDO = merchantApplicationService.queryAppChannelDetail(merchantPaymentQueryDto.getMerchantPaymentDO().getMchChannelCode());
        ChannelPaymentContext channelPaymentContext = new ChannelPaymentContext();
        channelPaymentContext.setMerchantAppChannelDO(merchantAppChannelDO);
        channelPaymentContext.setMerchantPaymentDetailDO(merchantPaymentDetailDO);
        channelPaymentContext.setMerchantPaymentDO(merchantPaymentQueryDto.getMerchantPaymentDO());
        channelPaymentContext.setProviderChannelConfig(providerService.queryConfig(merchantAppChannelDO.getProviderCode(), merchantAppChannelDO.getChannelCode()));
        channelPaymentContext.setTest(test);
        extensionExecutor.executeVoid(ChannelMerchantPaymentExtPt.class, BizScenario.valueOf(merchantAppChannelDO.getChannelCode()),
                ex -> ex.queryChannelPayment(channelPaymentContext));
        MerchantPaymentDetailDO updateMerchantPaymentDetailDO = channelPaymentContext.getUpdateMerchantPaymentDetailDO();
        if (StringUtils.hasText(channelPaymentContext.getRemark()) && !StringUtils.hasText(updateMerchantPaymentDetailDO.getRemark())) {
            updateMerchantPaymentDetailDO.setRemark(channelPaymentContext.getRemark());
        }
        return updateMerchantPaymentDetailDO;
    }

    @Override
    public ChannelPaymentCallBackDto parseCallBackText(String mchChannelCode, HttpServletRequest servletRequest) {
        MerchantAppChannelDO merchantAppChannelDO = merchantApplicationService.queryAppChannelDetail(mchChannelCode);
        ChannelPaymentCallBackContext channelPaymentCallBackContext = new ChannelPaymentCallBackContext();
        channelPaymentCallBackContext.setMerchantAppChannelDO(merchantAppChannelDO);
        channelPaymentCallBackContext.setProviderChannelConfig(providerService.queryConfig(merchantAppChannelDO.getProviderCode(), merchantAppChannelDO.getChannelCode()));
        return extensionExecutor.execute(ChannelMerchantPaymentExtPt.class, BizScenario.valueOf(merchantAppChannelDO.getChannelCode()),
                ex -> ex.parseCallBackText(channelPaymentCallBackContext, servletRequest));
    }

    @Override
    public ChannelPaymentCallBackDto parseCallBackTextFix(String channelCode, HttpServletRequest servletRequest) {
        return extensionExecutor.execute(ChannelMerchantPaymentExtPt.class, BizScenario.valueOf(channelCode), ex -> ex.parseCallBackTextFix(servletRequest));
    }

    @Override
    public boolean verify(String mchChannelCode, String accessCallBody) {
        MerchantAppChannelDO merchantAppChannelDO = merchantApplicationService.queryAppChannelDetail(mchChannelCode);
        ProviderChannelConfigDO providerChannelConfigDO = providerService.queryConfig(merchantAppChannelDO.getProviderCode(), merchantAppChannelDO.getChannelCode());
        return extensionExecutor.execute(ChannelMerchantPaymentExtPt.class, BizScenario.valueOf(merchantAppChannelDO.getChannelCode()),
                ex -> ex.verify(providerChannelConfigDO, merchantAppChannelDO, accessCallBody));
    }

    @Override
    public String processCallBack(String mchChannelCode, CommonResult commonResult) {
        MerchantAppChannelDO merchantAppChannelDO = merchantApplicationService.queryAppChannelDetail(mchChannelCode);
        return extensionExecutor.execute(ChannelMerchantPaymentExtPt.class, BizScenario.valueOf(merchantAppChannelDO.getChannelCode()),
                ex -> ex.processCallBack(merchantAppChannelDO, commonResult));
    }

    @Override
    public MerchantProofDto queryDetailProof(String mchChannelCode, MerchantPaymentDetailDO merchantPaymentDetailDO) {
        MerchantProofDto merchantProofDto = new MerchantProofDto();
        if (barrier) {
            log.info("测试环境查询代付凭证状态 detailPaymentId:{}", merchantPaymentDetailDO.getDetailPaymentId());
            return merchantProofDto;
        }
        MerchantAppChannelDO merchantAppChannelDO = merchantApplicationService.queryAppChannelDetail(mchChannelCode);
        ChannelPaymentContext channelPaymentContext = new ChannelPaymentContext();
        channelPaymentContext.setMerchantAppChannelDO(merchantAppChannelDO);
        channelPaymentContext.setMerchantPaymentDetailDO(merchantPaymentDetailDO);
        channelPaymentContext.setProviderChannelConfig(providerService.queryConfig(merchantAppChannelDO.getProviderCode(), merchantAppChannelDO.getChannelCode()));
        channelPaymentContext.setTest(test);
        extensionExecutor.executeVoid(ChannelMerchantPaymentExtPt.class, BizScenario.valueOf(merchantAppChannelDO.getChannelCode()),
                ex -> ex.queryChannelPaymentProof(channelPaymentContext));
        if (channelPaymentContext.isProcessStatus()) {
            // 解码 Base64 编码的 PDF 数据
            byte[] proof = null;
            if (StringUtils.hasText(channelPaymentContext.getVoucherFileStream())) {
                // 解码 Base64 编码的 PDF 数据
                proof = Base64.getDecoder().decode(channelPaymentContext.getVoucherFileStream());
            }
            else if (StringUtils.hasText(channelPaymentContext.getVoucherFileUrl())) {
                try {
                    proof = FileUtils.fileBytes(channelPaymentContext.getVoucherFileUrl());
                }
                catch (IOException e) {
                    log.error("代付凭证获取HTTP 文件失败:{}", e.getMessage());
                    return merchantProofDto;
                }
            }
            else if (channelPaymentContext.getVoucherFileByte() != null && channelPaymentContext.getVoucherFileByte().length > 0) {
                proof = channelPaymentContext.getVoucherFileByte();
            }
            CommonEnum.OssEnum fileType = channelPaymentContext.getFileType();
            if (fileType == null) fileType = CommonEnum.OssEnum.PDF;
            if (proof != null && proof.length > 0) {
                CommonResult<String> stringCommonResult = uploadService.uploadLocalFile(proof, merchantPaymentDetailDO.getDetailPaymentId(), fileType);
                if (stringCommonResult.isSuccess()) {
                    merchantProofDto.setProofPath(stringCommonResult.getData());
                    return merchantProofDto;
                }
            }
            else {
                merchantProofDto.setMsg(channelPaymentContext.getRemark());
                return merchantProofDto;
            }
        }
        else {
            if (StringUtils.hasText(channelPaymentContext.getFileId())) {
                merchantProofDto.setProofId(channelPaymentContext.getFileId());
            }
            merchantProofDto.setMsg(channelPaymentContext.getRemark());
        }
        return merchantProofDto;
    }

    @Override
    public boolean extensionExist(String mchChannelCode) {
        MerchantAppChannelDO merchantAppChannelDO = merchantApplicationService.queryAppChannelDetail(mchChannelCode);
        return extensionExecutor.executeExist(ChannelMerchantPaymentExtPt.class, BizScenario.valueOf(merchantAppChannelDO.getChannelCode()));
    }

    @Override
    public void notifyChannel(MerchantPaymentDO merchantPaymentDO, MerchantPaymentDetailDO merchantPaymentDetailDO) {
        if (barrier) {
            log.info("测试环境查询代付凭证状态 detailPaymentId:{}", merchantPaymentDetailDO.getDetailPaymentId());
            return;
        }
        MerchantAppChannelDO merchantAppChannelDO = merchantApplicationService.queryAppChannelDetail(merchantPaymentDO.getMchChannelCode());
        ChannelPaymentContext channelPaymentContext = new ChannelPaymentContext();
        channelPaymentContext.setMerchantAppChannelDO(merchantAppChannelDO);
        channelPaymentContext.setMerchantPaymentDO(merchantPaymentDO);
        channelPaymentContext.setMerchantPaymentDetailDO(merchantPaymentDetailDO);
        channelPaymentContext.setMerchantAccessDto(merchantAccessService.domainDetail(merchantAppChannelDO.getAccessId()));
        channelPaymentContext.setProviderChannelConfig(providerService.queryConfig(merchantAppChannelDO.getProviderCode(), merchantAppChannelDO.getChannelCode()));
        channelPaymentContext.setTest(test);
        extensionExecutor.executeVoid(ChannelMerchantPaymentExtPt.class, BizScenario.valueOf(merchantAppChannelDO.getChannelCode()), ex -> ex.notifyChannel(channelPaymentContext));
    }

    @Override
    public String submitChannelRecharge(MerchantPaymentDto merchantPaymentDto, MerchantPaymentDetailDO merchantPaymentDetailDO) {
        if (barrier) {
            log.info("测试环境提交代付 merchantPaymentDto:{}", merchantPaymentDto.getPaymentId());
            merchantPaymentDetailDO.setStatus(MerchantEnum.PROCESS_STATUS.FAIL.getCode());
            return "";
        }
        MerchantAppChannelDO merchantAppChannelDO = merchantApplicationService.queryAppChannelDetail(merchantPaymentDto.getMchChannelCode());
        ChannelPaymentContext channelPaymentContext = new ChannelPaymentContext();
        channelPaymentContext.setMerchantAppChannelDO(merchantAppChannelDO);
        channelPaymentContext.setMerchantPaymentDO(merchantPaymentDto);
        channelPaymentContext.setNotifyUrl(domain + MerchantExtConstant.MERCHANT_PAYMENT_ASYNC_FIX_CALLBACK_URL + merchantPaymentDto.getChannelCode());
        channelPaymentContext.setTest(test);
        channelPaymentContext.setProviderChannelConfig(providerService.queryConfig(merchantAppChannelDO.getProviderCode(), merchantAppChannelDO.getChannelCode()));
        channelPaymentContext.setMerchantPaymentDetailDO(merchantPaymentDetailDO);
        try {
            extensionExecutor.executeVoid(ChannelMerchantPaymentExtPt.class, BizScenario.valueOf(merchantAppChannelDO.getChannelCode()),
                    ex -> ex.submitChannelSingleRecharge(channelPaymentContext));
        }
        catch (Exception e) {
            channelPaymentContext.setProcessStatus(false);
            channelPaymentContext.setRemark(e.getMessage());
        }
        if (!channelPaymentContext.isProcessStatus()) {
            merchantPaymentDetailDO.setStatus(MerchantEnum.PROCESS_STATUS.FAIL.getCode());
            merchantPaymentDetailDO.setRemark(channelPaymentContext.getRemark());
        }
        else {
            merchantPaymentDetailDO.setStatus(MerchantEnum.PROCESS_STATUS.DOING.getCode());
            merchantPaymentDetailDO.setRemark(channelPaymentContext.getRemark());
        }
        if (channelPaymentContext.getPayChannelRequestDO() != null) {
            payChannelService.saveRequest(channelPaymentContext.getPayChannelRequestDO(), PayEnums.CHANNEL_REQUEST_TYPE.PAYMENT);
        }
        return channelPaymentContext.getRechargeUrl();
    }

    @Override
    public void notifyProofGenerate(MerchantPaymentDO merchantPaymentDO, MerchantPaymentDetailDO merchantPaymentDetailDO) {
        if (barrier) {
            log.info("测试环境凭证生成 merchantPaymentDO:{}", merchantPaymentDO.getPaymentId());
            return;
        }
        MerchantAppChannelDO merchantAppChannelDO = merchantApplicationService.queryAppChannelDetail(merchantPaymentDO.getMchChannelCode());
        ChannelPaymentContext channelPaymentContext = new ChannelPaymentContext();
        channelPaymentContext.setMerchantAppChannelDO(merchantAppChannelDO);
        channelPaymentContext.setMerchantPaymentDO(merchantPaymentDO);
        channelPaymentContext.setTest(test);
        channelPaymentContext.setProviderChannelConfig(providerService.queryConfig(merchantAppChannelDO.getProviderCode(), merchantAppChannelDO.getChannelCode()));
        channelPaymentContext.setMerchantPaymentDetailDO(merchantPaymentDetailDO);
        try {
            extensionExecutor.executeVoid(ChannelMerchantPaymentExtPt.class, BizScenario.valueOf(merchantAppChannelDO.getChannelCode()),
                    ex -> ex.notifyProofGenerate(channelPaymentContext));
        }
        catch (Exception e) {
            log.error("凭证生成 失败:{}", e.getMessage());
        }
    }

}
